﻿using codeparser.net.v2.Contracts;
using codeparser.net.v2.Exceptions;

using System;
using System.Collections.ObjectModel;
using System.Xml;

namespace codeparser.net.v2.Entities
{
    /// <summary>
    /// Represents a tag.
    /// </summary>
    public class Tag : IXmlWriter
    {
        /// <summary>
        /// Gets or sets the element.
        /// </summary>
        /// <value>The element.</value>
        public TagElement Element
        {
            get;
            set;
        }

        /// <summary>
        /// Contains the attributes.
        /// </summary>
        private Collection<TagAttribute> _attributes;

        /// <summary>
        /// Gets the attributes.
        /// </summary>
        /// <value>The attributes.</value>
        public Collection<TagAttribute> Attributes
        {
            get
            {
                return this._attributes;
            }
        }

        /// <summary>
        /// Gets or sets the style of the closing tag.
        /// </summary>
        /// <value>The style of the closing tag.</value>
        public TagClosingStyle ClosingTagStyle
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the style of the content.
        /// </summary>
        /// <value>The style of the content.</value>
        public TagContentStyle ContentStyle
        {
            get;
            set;
        }

        /// <summary>
        /// Contains the rules.
        /// </summary>
        private Collection<TagRule> _rules;
        
        /// <summary>
        /// Gets the rules.
        /// </summary>
        /// <value>The rules.</value>
        public Collection<TagRule> Rules
        {
            get
            {
                return this._rules;
            }
        }

        /// <summary>
        /// Initializes a new instance of the Tag type.
        /// </summary>
        /// <param name="element">The element.</param>
        public Tag(TagElement element)
            : this(element, TagClosingStyle.Required)
        {
        }

        /// <summary>
        /// Initializes a new instance of the Tag type.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="closingTagStyle">The style of the closing tag.</param>
        public Tag(TagElement element, TagClosingStyle closingTagStyle)
            : this(element, closingTagStyle, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the Tag type.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="attribute">The list of attributes.</param>
        public Tag(TagElement element, params TagAttribute[] attribute)
            : this(element, TagClosingStyle.Required, attribute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the Tag type.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="closingTagStyle">The style of the closing tag.</param>
        /// <param name="attribute">The list of attributes.</param>
        public Tag(TagElement element, TagClosingStyle closingTagStyle, params TagAttribute[] attribute)
            : this(element, closingTagStyle, TagContentStyle.Parse, attribute)
        {
        }

        /// <summary>
        /// Initializes a new instance of the Tag type.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="closingTagStyle">The style of the closing tag.</param>
        /// <param name="contentStyle">The style of the content.</param>
        /// <param name="attributes">The list of attributes.</param>
        public Tag(TagElement element, TagClosingStyle closingTagStyle, TagContentStyle contentStyle, params TagAttribute[] attributes)
        {
            // Initialize the fields.
            this.Element = element;
            this.ClosingTagStyle = closingTagStyle;
            this.ContentStyle = contentStyle;
            this._rules = new Collection<TagRule>();
            this._attributes = new Collection<TagAttribute>();

            if (attributes != null)
            {
                foreach (TagAttribute attribute in attributes)
                {
                    this.Attributes.Add(attribute);
                }
            }
        }

        /// <summary>
        /// Gets the best-matching rule for the specified attributes.
        /// </summary>
        /// <param name="attributes">The attributes.</param>
        /// <returns>The best-matching rule.</returns>
        internal TagRule GetRule(Collection<TagAttribute> attributes)
        {
            // Iterate over the rules.
            foreach (TagRule rule in this.Rules)
            {
                // If the current rule does not match the tag closing style, skip it.
                if ((this.ClosingTagStyle == TagClosingStyle.Required && rule.ClosingTagRule == null) ||
                    (this.ClosingTagStyle == TagClosingStyle.None && rule.ClosingTagRule != null))
                {
                    continue;
                }

                // Get the complete rule text.
                string completeRule = rule.OpeningTagRule + (rule.ClosingTagRule ?? "");

                // If the current rule does not contain every attribute that is specified, skip the
                // rule.
                bool tagNotFound = false;
                foreach (TagAttribute attribute in attributes)
                {
                    if (!completeRule.Contains("${" + attribute.Name + "}"))
                    {
                        tagNotFound = true;
                        break;
                    }

                    // Remove the attribute from the rule to prepare the final check.
                    completeRule = completeRule.Replace("${" + attribute.Name + "}", "");
                }
                if (tagNotFound)
                {
                    tagNotFound = false;
                    continue;
                }

                // Check that the rule does not contain more attributes than the requested
                // attributes.
                completeRule = completeRule.Replace("${content}", "");
                if (completeRule.Contains("${"))
                {
                    continue;
                }

                // Return the rule to the caller.
                return rule;
            }

            // If no rule has been found yet, throw an exception.
            throw new MissingRuleException("Rule missing for the given set of attributes");
        }

        /// <summary>
        /// Saves the tag to the specified XML writer.
        /// </summary>
        /// <param name="writer">The XML writer.</param>
        void IXmlWriter.Save(XmlWriter writer)
        {
            // Write the tag.
            writer.WriteStartElement("Tag");
            {
                writer.WriteAttributeString("ClosingTagStyle", this.ClosingTagStyle.ToString());
                writer.WriteAttributeString("ContentStyle", this.ContentStyle.ToString());

                // Write the tag's element.
                ((IXmlWriter)this.Element).Save(writer);

                // Write the tag's attributes.
                writer.WriteStartElement("Attributes");
                foreach (TagAttribute attribute in this._attributes)
                {
                    ((IXmlWriter)attribute).Save(writer);
                }
                writer.WriteEndElement();

                // Write the tag's rules.
                writer.WriteStartElement("Rules");
                foreach (TagRule rule in this._rules)
                {
                    ((IXmlWriter)rule).Save(writer);
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }
    }
}